package com.icbc.dccsh.bolt;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

import com.google.common.collect.Lists;
import com.icbc.dccsh.functions.FormulaEngine;
import com.icbc.dccsh.util.Watcher;

import backtype.storm.task.TopologyContext;
import backtype.storm.topology.BasicOutputCollector;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseBasicBolt;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;

public class FormulaCalcBolt extends BaseBasicBolt {
	private Watcher _watcher = null;
	private boolean _debugMode = false;

	public FormulaCalcBolt(boolean debugMode) {
		this._debugMode = debugMode;
	}

	@Override
	public void prepare(Map stormConf, TopologyContext context) {
		this._watcher = Watcher.getWatcher(this.getClass());
	}

	public void execute(Tuple tuple, BasicOutputCollector collector) {
		this._watcher.count();
		HashMap<String, Object> history_hash = null;

		HashMap<String, Object> input_hash = (HashMap<String, Object>) tuple.getValueByField("input");
		HashMap<String, Object> mapping_hash = (HashMap<String, Object>) tuple.getValueByField("mapping");
		if (tuple.contains("history")) {
			history_hash = (HashMap<String, Object>) tuple.getValueByField("history");
		}
		// here we put every known variables into the construction of
		// expression, no matter the variable exists in formula or not.
		// construct variable - value mapping ==>
		HashMap<String, Object> variableNameValueMapping = new HashMap<String, Object>();
		HashMap<String, Object> metrics_hash = (HashMap<String, Object>) input_hash.get("metrics");
		HashMap<String, Object> result_hash = new HashMap<String, Object>();
//		ArrayList mib_array = new ArrayList();
		for (Map.Entry<String, Object> metric : metrics_hash.entrySet()) {
			String metricName = metric.getKey();
			HashMap<String, Object> mibs = (HashMap<String, Object>) metric.getValue();
			for (Map.Entry<String, Object> mib : mibs.entrySet()) {
				String mibName = mib.getKey();
				Object mibValue = mib.getValue();
				variableNameValueMapping.put(mibName, mibValue);
			}
			if (history_hash != null) {
				HashMap<String, Object> history_metrics_hash = (HashMap<String, Object>) history_hash.get("metrics");
				HashMap<String, Object> mibs_history = (HashMap<String, Object>) history_metrics_hash.get(metricName);
				for (Map.Entry<String, Object> mib : mibs_history.entrySet()) {
					String mibName = mib.getKey() + "___prior";
					Object mibValue = mib.getValue();
					variableNameValueMapping.put(mibName, mibValue);
				}
			}

			HashMap<String, Object> metricMapping_hash = (HashMap<String, Object>) mapping_hash.get(metricName);
			String formula = (String) metricMapping_hash.get("formula");
//			if (StringUtils.isEmpty(formula)) {
//				System.out.println("formula cannot be empty...");
//				throw new RuntimeException("formual cannot be empty");
//			}

			Object result = null;
			// int threshold = (Integer)metricMapping_hash.get("threshold");
//			if (StringUtils.isEmpty(formula)) {
			if (FormulaEngine.IsSimpleFormula(formula)) {
				for (Map.Entry<String, Object> mib : mibs.entrySet()) {
					result = mib.getValue();
					break;
				}
			} else {
				FormulaEngine engine = FormulaEngine.GetFormulaEngine(formula);
				try {
					result = engine.variables(variableNameValueMapping).calculate();
				} catch (RuntimeException ex) {
					System.out.println(String.format("FormulaEngine throws error... details:...", ex.toString()));
					if (this._debugMode)
						throw ex;
				}
			}
			result_hash.put(metricName, result);
		}
		input_hash.put("result", result_hash);
		// List<Values> values = Lists.newArrayList();

		collector.emit("flatten_stream", new Values(input_hash));
		collector.emit("nested_stream", new Values(input_hash));

		/////////////////////////
		// build metric hash for input msg ==>
		// HashMap<String, Object> msg_metric_hash = new HashMap<String,
		// Object>();
		// ArrayList metric_arraylist = (ArrayList) input_hash.get("metrics");
		// // System.out.println("helloworld12345678...");
		// // System.out.println(input_hash);
		// if (metric_arraylist == null) {
		// System.out.println(input_hash);
		// }
		// for (Object metric : metric_arraylist) {
		// HashMap<String, Object> metricMapping = (HashMap<String, Object>)
		// metric;
		//
		// msg_metric_hash.put((String) metricMapping.get("name"),
		// metricMapping.get("value"));
		// }
		// // <==
		// if (tuple.contains("history")) {
		// history_hash = (HashMap<String, Object>)
		// tuple.getValueByField("history");
		// }
		// HashMap<String, Object> mapping_hash = (HashMap<String, Object>)
		// tuple.getValueByField("mapping");
		//
		// HashMap<String, Object> result_hash = new HashMap<String, Object>();
		// ArrayList metricList = (ArrayList) mapping_hash.get("metrics");
		// for (Object metricMapping_o : metricList) { // for each metric
		// HashMap<String, Object> metricMapping = (HashMap<String, Object>)
		// metricMapping_o;
		// String metricFormula = (String) metricMapping.get("formula"); // get
		// // metric
		// // formula
		// String metricName = (String) metricMapping.get("name"); // get
		// // metric
		// // name
		// ArrayList mibList = (ArrayList) metricMapping.get("mibs"); // get
		// // mib
		//
		// // here we put every known variables into the construction of
		// // expression, no matter the variable exists in formula or not.
		// // construct variable - value mapping ==>
		// HashMap<String, Object> variableNameValueMapping = new
		// HashMap<String, Object>();
		// // ArrayList mib_value_arraylist = (ArrayList)
		// // input_hash.get("value");
		// ArrayList mib_value_arraylist = (ArrayList)
		// msg_metric_hash.get(metricName);
		// if (mib_value_arraylist == null) {
		// System.out.println(String.format("Metric [%s] not found...",
		// metricName));
		// }
		// for (Object kve : mib_value_arraylist) {
		// HashMap<String, Object> kve_map = (HashMap<String, Object>) kve;
		// variableNameValueMapping.put((String) kve_map.get("mib_name"),
		// kve_map.get("mib_value"));
		// // Double.parseDouble((String) kve_map.get("mib_value")));
		// } // <==
		// // add history value into value mapping, history variable name
		// // will be suffixed as "___prior" ==>
		// if (history_hash != null) {
		// ArrayList history_metric_arraylist = (ArrayList)
		// history_hash.get("metrics");
		// if (history_metric_arraylist == null) {
		// System.out.println(history_hash);
		// }
		// for (Object metric : history_metric_arraylist) {
		// HashMap<String, Object> historyMetricMapping = (HashMap<String,
		// Object>) metric;
		//
		// ArrayList mib_value_arraylist_history = (ArrayList)
		// historyMetricMapping.get("value");
		// for (Object kve : mib_value_arraylist_history) {
		// HashMap<String, Object> kve_map = (HashMap<String, Object>) kve;
		// variableNameValueMapping.put((String) kve_map.get("mib_name") +
		// "___prior",
		// kve_map.get("mib_value"));
		// // Double.parseDouble((String)
		// // kve_map.get("mib_value")));
		// }
		// // msg_metric_hash.put((String)metricMapping.get("name"),
		// // metricMapping.get("value"));
		// }
		// // ArrayList mib_value_arraylist_history = (ArrayList)
		// // history_hash.get("value");
		// //
		// // for (Object kve : mib_value_arraylist_history) {
		// // HashMap<String, Object> kve_map = (HashMap<String, Object>)
		// // kve;
		// // variableNameValueMapping.put((String) kve_map.get("mib_name")
		// // + "___prior",
		// // kve_map.get("mib_value"));
		// //// Double.parseDouble((String) kve_map.get("mib_value")));
		// // }
		// }
		// // <==
		// Object result = null;
		// if (StringUtils.isEmpty(metricFormula)){
		// HashMap<String, Object> kv_map = (HashMap<String,
		// Object>)mib_value_arraylist.get(0);
		// result = kv_map.get("mib_value");
		//// result = variableNameValueMapping.get(metricName);
		// } else {
		// FormulaEngine engine = FormulaEngine.GetFormulaEngine(metricFormula);
		// try {
		// result = engine.variables(variableNameValueMapping).calculate();
		// } catch (RuntimeException ex) {
		// System.out.println(String.format("FormulaEngine throws error...
		// details:...", ex.toString()));
		// if (this._debugMode)
		// throw ex;
		// }
		// }
		//
		// result_hash.put(metricName, result);
		// }
		// input_hash.put("result", result_hash);
		// // List<Values> values = Lists.newArrayList();
		//
		// collector.emit("flatten_stream", new Values(input_hash));
		// collector.emit("nested_stream", new Values(input_hash));
	}

	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		// declarer.declare(new Fields("result"));
		declarer.declareStream("flatten_stream", new Fields("result"));
		declarer.declareStream("nested_stream", new Fields("result"));
	}
}
